/*
 * Copyright 2007-2008 Andrew O'Malley
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package ws.quokka.plugin.resources;

import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Copy;
import org.apache.tools.ant.types.FilterSet;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.ResourceCollection;
import org.apache.tools.ant.types.resources.FileResource;

import ws.quokka.core.bootstrap_util.Assert;
import ws.quokka.core.bootstrap_util.IOUtils;
import ws.quokka.core.bootstrap_util.Logger;
import ws.quokka.core.bootstrap_util.PropertiesUtil;
import ws.quokka.core.plugin_spi.support.AntUtils;
import ws.quokka.core.plugin_spi.support.TypedProperties;

import java.io.File;

import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;


/**
 * ResourcesCopier factors out the copying with filtering functionality so that it can be reused by
 * other plugins the require resources to be copied.
 */
public class ResourcesCopier {
    //~ Instance fields ------------------------------------------------------------------------------------------------

    private Project project;
    private AntUtils antUtils;
    private IOUtils utils = new IOUtils();
    private Logger logger;

    //~ Constructors ---------------------------------------------------------------------------------------------------

    public ResourcesCopier(Project project, Logger logger) {
        this.project = project;
        this.antUtils = new AntUtils(project);
        this.logger = logger;
    }

    //~ Methods --------------------------------------------------------------------------------------------------------

    public void copy(Collection resources, File destination) {
        copy(resources, destination, null, null, null, false);
    }

    public void copy(Collection resources, File destination, Collection filters, File tokensFile,
        String ignoredTokensPrefix, boolean addPropertiesAsTokens) {
        resources = antUtils.filterExisting(resources);

        if (resources.size() == 0) {
            return; // No resources to copy
        }

        Copy copy = (Copy)project.createTask("copy");
        copy.setTodir(destination);
        copy.setFiltering(false);

        for (Iterator i = resources.iterator(); i.hasNext();) {
            ResourceCollection rc = (ResourceCollection)i.next();
            copy.add(rc);
        }

        if (filters != null) {
            // Apply filtering
            copy.setFiltering(true);

            FilterSet filterSet = copy.createFilterSet();

            Properties tokens = getTokens(filters, addPropertiesAsTokens);
            boolean match = match(tokens, tokensFile, ignoredTokensPrefix);

            if (!match) {
                logger.verbose("Forcing resources overwrite as tokens have changed");
                copy.setOverwrite(true);
                antUtils.mkdir(tokensFile.getParentFile());
                utils.saveProperties(tokensFile, tokens);
            }

            for (Iterator i = tokens.entrySet().iterator(); i.hasNext();) {
                Map.Entry entry = (Map.Entry)i.next();
                String key = (String)entry.getKey();
                String value = (String)entry.getValue();
                filterSet.addFilter(key, value);
                filterSet.addFilter(key + "--norm", normalisePath(value));
                filterSet.addFilter(key + "--esc", escape(value));
                filterSet.addFilter(key + "--norm,esc", escape(normalisePath(value)));
            }
        }

        copy.perform();
    }

    private boolean match(Properties tokens, File tokensFile, String ignoredTokensPrefix) {
        if (!tokensFile.exists()) {
            return false;
        }

        TypedProperties tp = new TypedProperties(ignoredTokensPrefix, PropertiesUtil.getProperties(project), project);
        Map ignored = tp.getMap("ignoredTokens", false, String.class);

        // Force match if any tokens have changed
        boolean match = true;
        Properties oldTokens = utils.loadProperties(tokensFile);

        for (Iterator i = tokens.entrySet().iterator(); i.hasNext();) {
            Map.Entry entry = (Map.Entry)i.next();
            String key = (String)entry.getKey();

            if (ignored.containsKey(key)) {
                continue;
            }

            String value = (String)entry.getValue();
            String oldValue = (String)oldTokens.remove(key);

            if (oldValue == null) {
                match = false;
                logger.verbose("New token: key=" + key + ", value=" + value);
            } else {
                if (!value.equals(oldValue)) {
                    match = false;
                    logger.verbose("Token changed: key=" + key + "\n\told=" + oldValue + "\n\tnew=" + value);
                }
            }
        }

        for (Iterator i = oldTokens.entrySet().iterator(); i.hasNext();) {
            Map.Entry entry = (Map.Entry)i.next();
            String key = (String)entry.getKey();

            if (ignored.containsKey(key)) {
                continue;
            }

            match = false;
            logger.verbose("Token removed: key=" + key + ", value=" + entry.getValue());
        }

        return match;
    }

    private Properties getTokens(Collection filters, boolean addPropertiesAsTokens) {
        Properties tokens = new Properties();

        if (addPropertiesAsTokens) {
            tokens.putAll(PropertiesUtil.getProperties(project));
        }

        for (Iterator i = filters.iterator(); i.hasNext();) {
            ResourceCollection filter = (ResourceCollection)i.next();
            tokens.putAll(getTokens(filter));
        }

        return tokens;
    }

    private String escape(String value) {
        StringBuffer sb = new StringBuffer();

        for (int i = 0; i < value.length(); i++) {
            char ch = value.charAt(i);

            if (ch == '\\') {
                sb.append('\\');
            }

            sb.append(ch);
        }

        return sb.toString();
    }

    private String normalisePath(String value) {
        try {
            Path path = new Path(project, value);

            return path.toString();
        } catch (Exception e) {
            e.printStackTrace();

            return value; // Not a valid path
        }
    }

    private Map getTokens(ResourceCollection filters) {
        Assert.isTrue(filters.isFilesystemOnly(),
            "Only file system resource collections are supported for resource copying");

        Properties tokens = new Properties();

        if (antUtils.containsFiles(filters, true)) {
            for (Iterator i = filters.iterator(); i.hasNext();) {
                FileResource fileResource = (FileResource)i.next();
                tokens.putAll(utils.loadProperties(fileResource.getFile()));
            }
        }

        return tokens;
    }
}
